
import java.util.Vector;

class SocialGroup extends Entity {  

  // PARAMETERS

  // affects the positioning of the individuals
  final float centerPoint = 0.5;
  final float randomSpan = 0.30;

  // springs that keeps the individuals in place
  final float springStrenght = 0.3;
  final float springDamping = 0.8;

  int numAnchorPoints;

  final float anchorRadiusMax = 250.0;
  final float anchorRadiusMin = 90.0;

  final float probExistingConnection = 0.9;
  final float probNewConnection = 0.08;
  final float probInterGroupConnection = 0.02;

  final float probAutoPull = 0.0002;

  final float bonusForExistingConnection = 0.5;

  static final int EXISTING = 1;
  static final int NEWCONNECTION = 2;
  static final int INTERGROUP = 3;

  final color fillColorMembraneInner = color(243, 239, 238);
  final color fillColorMembrane = color(223, 219, 218);
  final color strokeColorMembrane = color(54, 56, 42);

  final float hilightOpacity = 240.0;
  final float idleOpacity = 100.0;
  
  // PARAMETERS END


  // MEMBERS
  Vector individuals;
  Vector connections;
  Vector anchorPoints;
  Vector springs;
  float anchorRadius;
  public float movingx, movingy;
  boolean isFrozen = false;

  boolean renderAnchorPoints = false;
  boolean renderCenterPoint = false;
  boolean renderAnchorRadius = false;
  boolean renderMembrane = true;
  // END MEMBERS


  public SocialGroup(Particle p, int numInds) {
    super(p);

    individuals = new Vector();
    connections = new Vector();
    anchorPoints = new Vector();
    springs = new Vector();

    // create the anchor points
    numAnchorPoints = numInds;

    float x,y;
    float radStepSize = TWO_PI / (float)numAnchorPoints;

    if(numInds < minIndividuals) {
      println(
          "Warning. Got to few individuals when creating group (" + 
          numInds + "). Changing to minimum value of " + minIndividuals);
      numInds = minIndividuals;
    }

    anchorRadius = anchorRadiusMin + 
      (anchorRadiusMax-anchorRadiusMin) * ((float)numInds / maxIndividuals);
    
    for(int i=0; i<numAnchorPoints; i++) {
      x = p.position().x() + sin(radStepSize * i) * 
        anchorRadius;
      y = p.position().y() + cos(radStepSize * i) * 
        anchorRadius;

      Entity ap = new Entity();
      ap.p = ps.makeParticle(10, x, y, 0);
      ap.p.makeFixed();
      anchorPoints.add(ap);
    }

    // dont let the sg be affected by forces
    p.makeFixed();

    setNumberOfIndividuals(numInds);
  }

  public void setNumberOfIndividuals(int num) {
    individuals.clear();
    connections.clear();

    for(int i=0; i<num; i++) {
      Individual spawn = new Individual(this);

      // get an anchor point
      Entity ap = (Entity)anchorPoints.get(int(i / float(num) * numAnchorPoints));

      // create spring to an anchorpoint
      // distribute the ap:s evenly between all created individuals
      float point = centerPoint + fixedRandom(randomSpan) - randomSpan/2;

      // calculate distance from center to ap
      float tx = absx() - ap.absx();
      float ty = absy() - ap.absy();
      float len = sqrt(tx*tx + ty*ty);

      float spawnDist = len * point;

      spawn.angle = atan2(tx, ty);
      spawn.distance = spawnDist;

      // spring to anchor points
      springs.add(ps.makeSpring(spawn.p,
            ap.p,
            springStrenght,
            springDamping,
            spawnDist));

      // spring to center
      springs.add(ps.makeSpring(spawn.p,
            p,
            springStrenght,
            springDamping,
            (len - spawnDist)));

      individuals.add(spawn);
    }
  }

  public void randomMemeDrop(boolean superMeme) {
    int idx = (int)fixedRandom(individuals.size());

    Individual pick = (Individual)individuals.get(idx);
    Meme cloudMeme = theCloud.getMeme();

    if(superMeme)
      cloudMeme.spreadBonus = 1.0;
    else
      cloudMeme.spreadBonus = 0.0;

    connections.add(new Connection(this, cloudMeme, pick));
  }
  
  // get spread victims without existing connections and
  // that doesn't have the meme
  public Individual getConnectionCandidate(Individual from, Meme forMeme) {
    Vector possibleInds = (Vector)individuals.clone();

    // dont spread to self
    possibleInds.remove(from); 

    // dont spread to individuals that are already connected
    for(int i=0; i < from.connections.size(); i++)
      possibleInds.remove(((Connection)from.connections.get(i)).other(from));

    // dont spread to individuals that have the meme
    for(int i=0; i<individuals.size(); i++) {
      Individual other = getInd(i);

      if(other == from) continue; // skip self

      if(other.hasMeme(forMeme)) {
        possibleInds.remove(other);
        continue;
      }
    }

    // make sure there were anyone to spread to
    if(possibleInds.size() < 1) return null;

    // pick a random one from the avaiable individuals
    return (Individual)possibleInds.get(
        (int)fixedRandom(possibleInds.size() - 1));
  }

  private boolean spreadNewConnection(Individual from, Meme meme) {

    // dont spread if maximum connections is reached
    if(from.createdConnections >= from.maxConnections)
      return false;

    Individual pick = getConnectionCandidate(from, meme);

    if(pick == null) {
      return false;
    }
    else {
      connections.add(new Connection(this, from, pick, meme));
      return true;
    }
  }

  private boolean spreadInterGroup(Individual from, Meme meme) {

    // dont spread if maximum connections is reached
    if(from.createdConnections >= from.maxConnections)
      return false;
    
    SocialGroup otherGroup = getInterGroup(this);
    Individual pick = otherGroup.getConnectionCandidate(from, meme);

    if(pick == null) {
      return false;
    }
    else {
      connections.add(new Connection(this, from, pick, meme));
      return true;
    }
  }

  private boolean spreadExisting(Individual from, Meme meme) {
    Connection foundConnection = null;
    Vector availCons = new Vector();
    float totalArea = 0.0;
    float offset = 0.0;
    Connection con = null;

    // gather a list of possible connections to use
    // criterions: 
    // is idle and the individual at the other end 
    // does not have the meme
    for(int i=0; i < from.connections.size(); i++) {
      Connection c = ((Connection)from.connections.get(i));

      if(c.viableForSpread(from, meme)) {
        availCons.add(c);
        totalArea += c.size;
      }
    }

    // if there were no suitable connections, fail
    if(availCons.size() < 1) return false;

    // decide on place in the total area to place the mark
    float mark = fixedRandom(totalArea);

    // find out wich connection the mark landed on
    for(int i=0; i < availCons.size(); i++) {
      float thisArea = mark - offset;

      con = ((Connection)availCons.get(i));
      if( (thisArea >= 0) && (thisArea <= con.size)) {
        foundConnection = con;
        break;
      }
      offset += con.size;
    }

    // ensure we actually got a connection
    if(foundConnection == null) return false;

    // make the connection face the right way
    if(foundConnection.from != from) 
      foundConnection.flip();

    foundConnection.startSpread(meme, bonusForExistingConnection);
    return true;
  }

  // individual wants to spread a meme
  public void requestSpreadChannel(Individual from, Meme meme) {

    // who to spread the meme to
    Individual pick = null;

    float randomPath = fixedRandom(1.0);
    int spreadPath = 0;

    if(randomPath < probExistingConnection)
      spreadPath = EXISTING;
    else if (randomPath >= (1 - probInterGroupConnection))
      spreadPath = INTERGROUP;
    else
      spreadPath = NEWCONNECTION;

    // the target as a potiential spreader
    meme.addSpreader();

    // select priority of spreading path
    switch(spreadPath) {
      case EXISTING:
        if(spreadExisting(from, meme)) return;
        if(spreadNewConnection(from, meme)) return;

        // favor supermemes intergroup spreading when 
        // no other options exists
        if(random(0.0, 1.0) < meme.spreadBonus)
          if(spreadInterGroup(from, meme)) return;
        break;

      case NEWCONNECTION:
        if(spreadNewConnection(from, meme)) return;
        if(spreadExisting(from, meme)) return;
        if(spreadInterGroup(from, meme)) return;
        break;

      case INTERGROUP:
        if(spreadInterGroup(from, meme)) return;
        if(spreadNewConnection(from, meme)) return;
        if(spreadExisting(from, meme)) return;
        break;
    }

    // println("Did not find a suitable path to spread on. Ignoring");
    // remove the target as a potential spreader
    meme.removeSpreader(from);
    return;
  }

  public Individual getInd(int i) {
    // if(i < 0 || i > individuals.size() - 1) return null;
    return (Individual)individuals.get(i);
  }

  public Connection getCon(int i) {
    //if(i < 0 || i > connections.size() - 1) return null;
    return (Connection)connections.get(i);
  }

  public void moveDynamic(float dx, float dy) {
    p.moveBy(dx, dy, 0);

    for(int i=0; i<anchorPoints.size(); i++) {
      ((Entity)anchorPoints.get(i)).p.moveBy(dx, dy, 0);
    }
  }

  public void moveStatic(float dx, float dy) {
    moveDynamic(dx, dy);

    for(int i=0; i < individuals.size(); i++) {
      Individual ind =  ((Individual)individuals.get(i));
      ind.p.moveBy(dx, dy, 0);
    }
  }

  private void setFixed(boolean fixed) {

    if(isFrozen == fixed) return;
    isFrozen = fixed;

    for(int i=0; i < springs.size(); i++) {

      Spring s = (Spring)springs.get(i);

      if(fixed) {
        s.turnOff();
        s.getOneEnd().makeFixed();
      }  
      else {
        s.turnOn();
        s.getOneEnd().makeFree();      
      }
    }
  }

  public void update() {

    // update individuals
    for(int i=0; i<individuals.size(); i++) {
      getInd(i).update();
    }

    // update the connections
    for(int i=0; i < connections.size(); i++) {
      getCon(i).update();
    }

    if(autoPulling) {
      if(random(1) < probAutoPull)
        randomMemeDrop(false);
    }
  }

  public void drawDebugInfo() {
    // draw misc debug stuff
    float crossSize = 50;
    if(renderCenterPoint) {
      stroke(c);
      strokeWeight(1);
      line(x() - crossSize, y(), x() + crossSize, y());
      line(x(), y() - crossSize, x(), y() + crossSize);
    }

    if(renderAnchorPoints) {
      stroke(0);
      strokeWeight(1);
      noFill();
      for(int i=0; i<anchorPoints.size(); i++) {
        Entity ap = ((Entity)anchorPoints.get(i));
        rect(ap.x(), ap.y(), 10, 10);
      }
    }

    if(renderAnchorRadius) {
      stroke(63, 63, 63);
      strokeWeight(2);
      noFill();
      ellipse(x(), y(), anchorRadius, anchorRadius);
    }
  }

  public void drawIndividuals() {
    for(int i=0; i<individuals.size(); i++)
      getInd(i).draw();
  }

  public void drawConnections() {
    // draw the stroke
    for(int i=0; i<connections.size(); i++)
      getCon(i).draw();
  }

  public void drawMembrane() {

    if(outsideClip()) return;
    boolean macroMode = zoom > macroZoom;

    boolean hasActiveMeme = false;

    // set flag for spreading individuals within group
    for(int i=0; i < individuals.size(); i++) {
      if(getInd(i).activeMeme != null) {
        hasActiveMeme = true;
        break;
      }
    }

    // draw outside line
    if(macroMode) {
      noStroke();
      fill(fillColorMembrane, 200);
      drawMembraneHull(30);   
    }

    // draw outer membrane
    strokeWeight(3);
    stroke(mergeColor(backgroundColor, 
          strokeColorMembrane, 
          (hasActiveMeme ? hilightOpacity : idleOpacity) / 255));
    fill(fillColorMembrane);
    drawMembraneHull(25);

    /* draw inside line
    if(macroMode) {
      noStroke();
      fill(fillColorMembraneInner);
      drawMembraneHull(5);   
    } */
  }

  private void drawMembraneHull(float offset) {
    float x, y;
    Individual ind;

    // draw the filling
    Individual firstInd = 
      ((Individual)individuals.get(0));
    Individual lastInd = 
      ((Individual)individuals.get(individuals.size() - 1));

    beginShape();

      x = lastInd.x() - sin(lastInd.angle) * (offset + size);
      y = lastInd.y() - cos(lastInd.angle) * (offset + size);
      curveVertex(x + 0.001, y - 0.001);
    
      for(int i=0; i<individuals.size(); i++) {
        ind = ((Individual)individuals.get(i));

        x = ind.x() - sin(ind.angle) * (offset + size);
        y = ind.y() - cos(ind.angle) * (offset + size);
        curveVertex(x, y);
      }
      
      x = firstInd.x() - sin(firstInd.angle) * (offset + size);
      y = firstInd.y() - cos(firstInd.angle) * (offset + size);
      curveVertex(x + 0.001, y - 0.001);

      x = lastInd.x() - sin(lastInd.angle) * (offset + size);
      y = lastInd.y() - cos(lastInd.angle) * (offset + size);
      curveVertex(x - 0.001, y + 0.001);

    endShape();
  }  
}
